home *** CD-ROM | disk | FTP | other *** search
/ PC Advisor 2011 May / PC Advisor 190 E.iso / pc / ESSENTIALS / VLC Media Player 1.1 / vlc-1.1.5-win32.exe / lua / extensions / imdb.lua < prev    next >
Encoding:
Text File  |  2010-11-13  |  11.8 KB  |  363 lines

  1. --[[
  2.  Get information about a movie from IMDb
  3.  
  4.  Copyright ┬⌐ 2009-2010 VideoLAN and AUTHORS
  5.  
  6.  Authors:  Jean-Philippe Andr├⌐ (jpeg@videolan.org)
  7.  
  8.  This program is free software; you can redistribute it and/or modify
  9.  it under the terms of the GNU General Public License as published by
  10.  the Free Software Foundation; either version 2 of the License, or
  11.  (at your option) any later version.
  12.  
  13.  This program is distributed in the hope that it will be useful,
  14.  but WITHOUT ANY WARRANTY; without even the implied warranty of
  15.  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  16.  GNU General Public License for more details.
  17.  
  18.  You should have received a copy of the GNU General Public License
  19.  along with this program; if not, write to the Free Software
  20.  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
  21. --]]
  22.  
  23. -- TODO: Use simplexml module to simplify parsing
  24.  
  25. -- Global variables
  26. url = nil          -- string
  27. title = nil        -- string
  28. titles = {}        -- table, see code below
  29.  
  30. -- Some global variables: widgets
  31. dlg = nil          -- dialog
  32. txt = nil          -- text field
  33. list = nil         -- list widget
  34. button_open = nil  -- button widget
  35. html = nil         -- rich text (HTML) widget
  36. waitlbl = nil      -- text label widget
  37.  
  38. -- Script descriptor, called when the extensions are scanned
  39. function descriptor()
  40.     return { title = "IMDb - The Internet Movie Database" ;
  41.              version = "1.0" ;
  42.              author = "Jean-Philippe Andr├⌐" ;
  43.              url = 'http://www.imdb.org/';
  44.              shortdesc = "The Internet Movie Database";
  45.              description = "<center><b>The Internet Movie Database</b></center><br />"
  46.                         .. "Get information about movies from the Internet "
  47.                         .. "Movie Database (IMDb).<br />This Extension will show "
  48.                         .. "you the cast, a short plot summary and a link to "
  49.                         .. "the web page on imdb.org." ;
  50.              capabilities = { "input-listener" } }
  51. end
  52.  
  53. -- Remove trailing & leading spaces
  54. function trim(str)
  55.     if not str then return "" end
  56.     return string.gsub(str, "^%s*(.*)+%s$", "%1")
  57. end
  58.  
  59. -- Update title text field. Removes file extensions.
  60. function update_title()
  61.     local item = vlc.input.item()
  62.     local name = item and item:name()
  63.     if name ~= nil then
  64.         name = string.gsub(name, "(.*)(%.%w+)$", "%1")
  65.     end
  66.     if name ~= nil then
  67.         txt:set_text(trim(name))
  68.     end
  69. end
  70.  
  71. -- Function called when the input (media being read) changes
  72. function input_changed()
  73.     update_title()
  74. end
  75.  
  76. -- First function to be called when the extension is activated
  77. function activate()
  78.     create_dialog()
  79. end
  80.  
  81. -- This function is called when the extension is disabled
  82. function deactivate()
  83. end
  84.  
  85. -- Create the main dialog with a simple search bar
  86. function create_dialog()
  87.     dlg = vlc.dialog("IMDb")
  88.     dlg:add_label("<b>Movie Title:</b>", 1, 1, 1, 1)
  89.     local item = vlc.input.item()
  90.     txt = dlg:add_text_input(item and item:name() or "", 2, 1, 1, 1)
  91.     dlg:add_button("Search", click_okay, 3, 1, 1, 1)
  92.     -- Show, if not already visible
  93.     dlg:show()
  94. end
  95.  
  96. -- Dialog closed
  97. function close()
  98.     -- Deactivate this extension
  99.     vlc.deactivate()
  100. end
  101.  
  102. -- Called when the user presses the "Search" button
  103. function click_okay()
  104.     vlc.msg.dbg("[IMDb] Searching for " .. txt:get_text())
  105.  
  106.     -- Search IMDb: build URL
  107.     title = string.gsub(string.gsub(txt:get_text(), "[%p%s%c]", "+"), "%++", " ")
  108.     url = "http://www.imdb.com/find?s=all&q=" .. string.gsub(title, " ", "+")
  109.  
  110.     -- Recreate dialog structure: delete useless widgets
  111.     if html then
  112.         dlg:del_widget(html)
  113.         html = nil
  114.     end
  115.  
  116.     if list then
  117.         dlg:del_widget(list)
  118.         dlg:del_widget(button_open)
  119.         list = nil
  120.         button_open = nil
  121.     end
  122.  
  123.     -- Ask the user to wait some time...
  124.     local waitmsg = 'Searching for <a href="' .. url .. '">' .. title .. "</a> on IMDb..."
  125.     if not waitlbl then
  126.         waitlbl = dlg:add_label(waitmsg, 1, 2, 3, 1)
  127.     else
  128.         waitlbl:set_text(waitmsg)
  129.     end
  130.     dlg:update()
  131.  
  132.     -- Download the data
  133.     local s, msg = vlc.stream(url)
  134.     if not s then
  135.         vlc.msg.warn("[IMDb] " .. msg)
  136.         waitlbl:set_text('Sorry, an error occured while searching for <a href="'
  137.                          .. url .. '">' .. title .. "</a>.<br />Please try again later.")
  138.         return
  139.     end
  140.  
  141.     -- Fetch HTML data
  142.     local data = s:read(65000)
  143.     if not data then
  144.         vlc.msg.warn("[IMDb] Not data received!")
  145.         waitlbl:set_text('Sorry, an error occured while searching for <a href="'
  146.                          .. url .. '">' .. title .. "</a>.<br />Please try again later.")
  147.         return
  148.     end
  149.  
  150.     -- Probe result & parse it
  151.     if string.find(data, "<h6>Overview</h6>") then
  152.         -- We found a direct match
  153.         parse_moviepage(data)
  154.     else
  155.         -- We have a list of results to parse
  156.         parse_resultspage(data)
  157.     end
  158. end
  159.  
  160. -- Called when clicked on the "Open" button
  161. function click_open()
  162.     -- Get user selection
  163.     selection = list:get_selection()
  164.     if not selection then return end
  165.  
  166.     local sel = nil
  167.     for idx, selectedItem in pairs(selection) do
  168.         sel = idx
  169.         break
  170.     end
  171.     if not sel then return end
  172.     local imdbID = titles[sel].id
  173.  
  174.     -- Update information message
  175.     url = "http://www.imdb.org/title/" .. imdbID .. "/"
  176.     title = titles[sel].title
  177.  
  178.     dlg:del_widget(list)
  179.     dlg:del_widget(button_open)
  180.     list = nil
  181.     button_open = nil
  182.     waitlbl:set_text("Loading IMDb page for <a href=\"" .. url .. "\">" .. title .. "</a>.")
  183.     dlg:update()
  184.  
  185.     local s, msg = vlc.stream(url)
  186.     if not s then
  187.         waitlbl:set_text('Sorry, an error occured while looking for <a href="'
  188.                          .. url .. '">' .. title .. "</a>.")
  189.         vlc.msg.warn("[IMDb] " .. msg)
  190.         return
  191.     end
  192.  
  193.     data = s:read(65000)
  194.     if data and string.find(data, "<h6>Overview</h6>") then
  195.         parse_moviepage(data)
  196.     else
  197.         waitlbl:set_text('Sorry, no results found for <a href="'
  198.                          .. url .. '">' .. title .. "</a>.")
  199.     end
  200. end
  201.  
  202. -- Parse the results page and find titles, years & URL's
  203. function parse_resultspage(data)
  204.     vlc.msg.dbg("[IMDb] Analysing results page")
  205.  
  206.     -- Find titles
  207.     titles = {}
  208.     local count = 0
  209.  
  210.     local idxEnd = 1
  211.     while idxEnd ~= nil do
  212.         -- Find title types
  213.         local titleType = nil
  214.         _, idxEnd, titleType = string.find(data, "<b>([^<]*Titles[^<]*)</b>", idxEnd)
  215.         local _, _, nextTitle = string.find(data, "<b>([^<]*Titles[^<]*)</b>", idxEnd)
  216.         if not titleType then
  217.             break
  218.         else
  219.             -- Find current scope
  220.             local table = nil
  221.             if not nextTitle then
  222.                 _, _, table = string.find(data, "<table>(.*)</table>", idxEnd)
  223.             else
  224.                 nextTitle = string.gsub(nextTitle, "%(", "%%(")
  225.                 nextTitle = string.gsub(nextTitle, "%)", "%%)")
  226.                 _, _, table = string.find(data, "<table>(.*)</table>.*"..nextTitle, idxEnd)
  227.             end
  228.  
  229.             if not table then break end
  230.             local pos = 0
  231.             local thistitle = nil
  232.  
  233.             -- Find all titles in this scope
  234.             while pos ~= nil do
  235.                 local _, _, link = string.find(table, "<a href=\"([^\"]+title[^\"]+)\"", pos)
  236.                 if not link then break end -- this would not be normal behavior...
  237.                 _, pos, thistitle = string.find(table, "<a href=\"" .. link .. "\"[^>]*>([^<]+)</a>", pos)
  238.                 if not thistitle then break end -- this would not be normal behavior...
  239.                 local _, _, year = string.find(table, "\((%d+)\)", pos)
  240.                 -- Add this title to the list
  241.                 count = count + 1
  242.                 local _, _, imdbID = string.find(link, "/([^/]+)/$")
  243.                 thistitle = replace_html_chars(thistitle)
  244.                 titles[count] = { id = imdbID ; title = thistitle ; year = year ; link = link }
  245.             end
  246.         end
  247.     end
  248.  
  249.     -- Did we find anything at all?
  250.     if not count or count == 0 then
  251.         waitlbl:set_text('Sorry, no results found for <a href="'
  252.                          .. url .. '">' .. title .. "</a>.")
  253.         return
  254.     end
  255.  
  256.     -- Sounds good, we found some results, let's display them
  257.     waitlbl:set_text(count .. " results found for <a href=\"" .. url .. "\">" .. title .. "</a>.")
  258.     list = dlg:add_list(1, 3, 3, 1)
  259.     button_open = dlg:add_button("Open", click_open, 3, 4, 1, 1)
  260.  
  261.     for idx, title in ipairs(titles) do
  262.         --list:add_value("[" .. title.id .. "] " .. title.title .. " (" .. title.year .. ")", idx)
  263.         list:add_value(title.title .. " (" .. title.year .. ")", idx)
  264.     end
  265. end
  266.  
  267. -- Parse a movie description page
  268. function parse_moviepage(data)
  269.     -- Title & year
  270.     title = string.gsub(data, "^.*<title>(.*)</title>.*$", "%1")
  271.     local text = "<h1>" .. title .. "</h1>"
  272.     text = text .. "<h2>Overview</h2><table>"
  273.  
  274.     -- Real URL
  275.     url = string.gsub(data, "^.*<link rel=\"canonical\" href=\"([^\"]+)\".*$", "%1")
  276.     local imdbID = string.gsub(url, "^.*/title/([^/]+)/.*$", "%1")
  277.     if imdbID then
  278.         url = "http://www.imdb.org/title/" .. imdbID .. "/"
  279.     end
  280.  
  281.     -- Director
  282.     local director = nil
  283.     _, nextIdx, _ = string.find(data, "<div id=\"director-info\"", 1, true)
  284.     if nextIdx then
  285.         _, _, director = string.find(data, "<a href[^>]+>([%w%s]+)</a>", nextIdx)
  286.     end
  287.     if not director then
  288.         director = "(Unknown)"
  289.     end
  290.     text = text .. "<tr><td><b>Director</b></td><td>" .. director .. "</td></tr>"
  291.  
  292.     -- Main genres
  293.     local genres = "<tr><td><b>Genres</b></td>"
  294.     local first = true
  295.     for genre, _ in string.gmatch(data, "/Sections/Genres/(%w+)/\">") do
  296.         if first then
  297.             genres = genres .. "<td>" .. genre .. "</td></tr>"
  298.         else
  299.             genres = genres .. "<tr><td /><td>" .. genre .. "</td></tr>"
  300.         end
  301.         first = false
  302.     end
  303.     text = text .. genres
  304.  
  305.     -- List main actors
  306.     local actors = "<tr><td><b>Cast</b></td>"
  307.     local first = true
  308.     for nm, char in string.gmatch(data, "<td class=\"nm\"><a[^>]+>([%w%s]+)</a></td><td class=\"ddd\"> ... </td><td class=\"char\"><a[^>]+>([%w%s]+)</a>") do
  309.         if not first then
  310.             actors = actors .. "<tr><td />"
  311.         end
  312.         actors = actors .. "<td>" .. nm .. "</td><td><i>" .. char .. "</i></td></tr>"
  313.         first = false
  314.     end
  315.     text = text .. actors .. "</table>"
  316.  
  317.     waitlbl:set_text("<center><a href=\"" .. url .. "\">" .. title .. "</a></center>")
  318.     if list then
  319.         dlg:del_widget(list)
  320.         dlg:del_widget(button_open)
  321.     end
  322.     html = dlg:add_html(text .. "<br />Loading summary...", 1, 3, 3, 1)
  323.     dlg:update()
  324.  
  325.     text = text .. "<h2>Plot Summary</h2>"
  326.     local s, msg = vlc.stream(url .. "plotsummary")
  327.     if not s then
  328.         vlc.msg.warn("[IMDb] " .. msg)
  329.         return
  330.     end
  331.     local data = s:read(65000)
  332.  
  333.     -- We read only the first summary
  334.     _, _, summary = string.find(data, "<p class=\"plotpar\">([^<]+)")
  335.     if not summary then
  336.         summary = "(Unknown)"
  337.     end
  338.     text = text .. "<p>" .. summary .. "</p>"
  339.     text = text .. "<p><h2>Source IMDb</h2><a href=\"" .. url .. "\">" .. url .. "</a></p>"
  340.  
  341.     html:set_text(text)
  342. end
  343.  
  344. -- Convert some HTML characters into UTF8
  345. function replace_html_chars(txt)
  346.     if not txt then return nil end
  347.     -- return vlc.strings.resolve_xml_special_chars(txt)
  348.     for num in string.gmatch(txt, "&#x(%x+);") do
  349.         -- Convert to decimal (any better way?)
  350.         dec = 0
  351.         for c in string.gmatch(num, "%x") do
  352.             cc = string.byte(c) - string.byte("0")
  353.             if (cc >= 10 or cc < 0) then
  354.                 cc = string.byte(string.lower(c)) - string.byte("a") + 10
  355.             end
  356.             dec = dec * 16 + cc
  357.         end
  358.         txt = string.gsub(txt, "&#x" .. num .. ";", string.char(dec))
  359.     end
  360.     return txt
  361. end
  362.  
  363.